﻿/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.server.functions;

import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.players.Player;
import com.chasekernan.hxnova.core.dataholders.MessageHandler;
import com.chasekernan.hxnova.core.stars.Star;

typedef Attacker = {
    var player : Player;
    var troops : Int;
}

class GroundCombat {
    
    public static var ATTACKER_BONUS = 1.1;
    
    /**
        Executes ground combat on the given star system. This automatically 
        handles take overs and messages as well.
        Will throw an error if, for each attacker, the [troops <= 0],
        the attacking player is null (or the null player), the star is null, 
        or the star is uninhabited.
        If there is a starbase at the star, then this does nothing.
    **/
    public static function resolveCombat(attackers : Array<Attacker>, 
            star : Star) {
        
        var samePlayer = new Array<Attacker>();
        for (attacker in attackers) {
            if (attacker.troops <= 0) {
                throw "Attacking troops must be greater than 0.";
            }
            if (attacker.player == null || 
                    attacker.player.id == Global.players.nullPlayer.id) {
                throw "Attacking player cannot be null (or the null player).";
            }
            
            //check to see if the player (using a different fleet) is attacking
            //and another attacker is recorded. If so, merge the two.
            for (a2 in attackers) {
                if (a2 == attacker) continue;
                
                if (a2.player.id == attacker.player.id) {
                    attacker.troops += a2.troops;
                    samePlayer.push(a2);
                }
            }
        }
        
        //remove all duplicate attackers that have been merged.
        for (a in samePlayer) attackers.remove(a);
        
        if (star == null || star.population == null || star.population <= 0) {
            throw "Star cannot be null or uninhabited.";
        }
        
        switch(star.starbase) {
            
            case None: //invasion possible
            
            default: 
                for(a in attackers) {
                    Global.messages.sendMessage( { 
                            to : Target.PlayerTarget(a.player), 
                            from : Target.Host, 
                            data : MessageData.CannotInvade(star.name)
                    });
                }
                return;
        }
        
        //determine the effective defense ground troop force (mutliplied by 
        //race attributes and defense multipliers).
        //TODO: Implement race ground combat multipliers
        var defenseBonus = 1 + 
               star.owner.defenseType.getGroundCombatProtection(star.defenses);
        
        var defendingForce : Float = star.population * defenseBonus;
        
        //determine the effective attacking ground troop force (mutliplied by 
        //race attributes and defense multipliers).
        //TODO: Implement race ground combat multipliers
        var attackingForceCombined = 0.0;
        
        for (a in attackers) {
            attackingForceCombined += a.troops * ATTACKER_BONUS;
        }
        
        var defenseLeft = Math.ceil(attackingForceCombined - defendingForce);
        
        var numCombatants = attackers.length + 1;
        
        if (Math.abs(defenseLeft) < 10) {
            //tie, no one wins.
            Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(star.owner), 
                        from : Target.Host, 
                        data : MessageData.TiedInvasion(star.name, 
                                                            numCombatants)
            });
            
            star.population = 0;
            star.checkPopulation();
            
            for (a in attackers) {
                Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(a.player), 
                        from : Target.Host, 
                        data : MessageData.TiedInvasion(star.name, 
                                                          numCombatants)
                });
            }
        } else if (defenseLeft > 0) {
            //re-adjust the defense to get the population left 
            //make sure there are at least 100 defenders
            star.population = Math.ceil(Math.max(defenseLeft / defenseBonus, 
                                                 100));
                                       
            Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(star.owner), 
                        from : Target.Host, 
                        data : MessageData.SuccessfulDefense(star.name, 
                                                            numCombatants)
            });
            
            for (a in attackers) {
                Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(a.player), 
                        from : Target.Host, 
                        data : MessageData.FailedInvasion(star.name, 
                                                          numCombatants)
                });
            }
        } else {
            //attackers won
            //if there are multiple attackers, pick a winner.
            Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(star.owner), 
                        from : Target.Host, 
                        data : MessageData.FailedDefense(star.name, 
                                                            numCombatants)
            });
            star.population = 0;
            star.checkPopulation();
            
            var winner = getMultiRaceWinner(attackers, defendingForce);
            star.owner = winner.player;
            star.population = winner.troops;
            
            Global.messages.sendMessage( { 
                    to : Target.PlayerTarget(winner.player), 
                    from : Target.Host, 
                    data : MessageData.SuccessfulInvasion(star.name, 
                                                              numCombatants)
            });
            
            for (a in attackers) {
                if (a.player == winner.player) continue;
                
                Global.messages.sendMessage( { 
                        to : Target.PlayerTarget(a.player), 
                        from : Target.Host, 
                        data : MessageData.FailedInvasion(star.name, 
                                                          numCombatants)
                });
            }
        }
        
    }
    
    /**
        Once the attackers have defeated the defender, the fight among 
        themselves, using this function. It determines a winner and 
        returns its troops left over. If no one wins, then this 
        returns null.
        Offset is the amount of defending forces.
        
        TODO: This is not how stars actually handles multiple races.
    **/
    public static function getMultiRaceWinner(races : Array < Attacker >, 
            offset : Float) : Attacker {
                
        var totalAttackForce = 0.0;
        //TODO: Put in race bonuses
        for (race in races) {
            totalAttackForce += race.troops * ATTACKER_BONUS;
        }
        
        var leftovers = totalAttackForce - offset;
        if (leftovers <= 0) return null;
        
        var largestAttackers = new Array<Attacker>();
        var amount = 0;
            
        for (race in races) {
            race.troops = Std.int(leftovers * 
                    (race.troops / totalAttackForce));
                
            if (race.troops > amount) {
                largestAttackers = [race];
                amount = race.troops;
            } else if(race.troops == amount) largestAttackers.push(race);
        }
        
        //if even number of top players, then no one wins
        if (largestAttackers.length % 2 == 0) return null;
        
        //otherwise pick a random winner and adjust the troop numbers
        var winner = largestAttackers[Std.random(largestAttackers.length)];
        //simplifying this -> (winner.troops / largestAttackers.length / 
        //ATTACKER_BONUS * (amount * largestAttackers.length / leftovers)) 
        //results in:
        winner.troops = Std.int(
                (winner.troops * winner.troops / ATTACKER_BONUS) 
              * (largestAttackers.length / leftovers));
              
        if (winner.troops == 0) return null;
        else return winner;
    }
    
}